#include "wx/wxprec.h"
#if wxUSE_DATEPICKCTRL

#include "wx/datectrl.h"

// use this version if we're explicitly requested to do it or if it's the only
// one we have
#if !defined(wxHAS_NATIVE_DATEPICKCTRL) || \
(defined(wxUSE_DATEPICKCTRL_GENERIC) && wxUSE_DATEPICKCTRL_GENERIC)

#ifndef WX_PRECOMP
#include "wx/dialog.h"
#include "wx/dcmemory.h"
#include "wx/panel.h"
#include "wx/textctrl.h"
#include "wx/valtext.h"
#endif

#ifdef wxHAS_NATIVE_DATEPICKCTRL
// this header is not included from wx/datectrl.h if we have a native
// version, but we do need it here
#include "wx/generic/datectrl.h"
#else
// we need to define _WX_DEFINE_DATE_EVENTS_ before including wx/dateevt.h to
// define the event types we use if we're the only date picker control version
// being compiled -- otherwise it's defined in the native version implementation
#define _WX_DEFINE_DATE_EVENTS_
#endif

#include "wx/dateevt.h"

#include "wx/calctrl.h"
#include "wx/combo.h"

// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

#define CALBORDER         4

// ----------------------------------------------------------------------------
// global variables
// ----------------------------------------------------------------------------


// ----------------------------------------------------------------------------
// local classes
// ----------------------------------------------------------------------------

class wxCalendarComboPopup : public wxCalendarCtrl,
  public wxComboPopup {
  public:

    wxCalendarComboPopup() : wxCalendarCtrl(),
      wxComboPopup() {
    }

    virtual void Init() {
    }

    // NB: Don't create lazily since it didn't work that way before
    //     wxComboCtrl was used, and changing behaviour would almost
    //     certainly introduce new bugs.
    virtual bool Create( wxWindow* parent ) {
      if( !wxCalendarCtrl::Create( parent, wxID_ANY, wxDefaultDateTime,
                                   wxPoint( 0, 0 ), wxDefaultSize,
                                   wxCAL_SHOW_HOLIDAYS | wxBORDER_SUNKEN ) ) {
        return false;
      }
      wxWindow *yearControl = wxCalendarCtrl::GetYearControl();
      wxClientDC dc( yearControl );
      dc.SetFont( yearControl->GetFont() );
      wxCoord width, dummy;
      dc.GetTextExtent( wxT( "2000" ), &width, &dummy );
      width += ConvertDialogToPixels( wxSize( 20, 0 ) ).x;
      wxSize calSize = wxCalendarCtrl::GetBestSize();
      wxSize yearSize = yearControl->GetSize();
      yearSize.x = width;
      wxPoint yearPosition = yearControl->GetPosition();
      SetFormat( wxT( "%x" ) );
      width = yearPosition.x + yearSize.x + 2 + CALBORDER / 2;
      if( width < calSize.x - 4 ) {
        width = calSize.x - 4;
      }
      int calPos = ( width - calSize.x ) / 2;
      if( calPos == -1 ) {
        calPos = 0;
        width += 2;
      }
      wxCalendarCtrl::SetSize( calPos, 0, calSize.x, calSize.y );
      yearControl->SetSize( width - yearSize.x - CALBORDER / 2, yearPosition.y,
                            yearSize.x, yearSize.y );
      wxCalendarCtrl::GetMonthControl()->Move( 0, 0 );
      m_useSize.x = width + CALBORDER / 2;
      m_useSize.y = calSize.y - 2 + CALBORDER;
      wxWindow* tx = m_combo->GetTextCtrl();
      if( !tx ) {
        tx = m_combo;
      }
      tx->Connect( wxEVT_KILL_FOCUS,
                   wxFocusEventHandler( wxCalendarComboPopup::OnKillTextFocus ),
                   NULL, this );
      return true;
    }

    virtual wxSize GetAdjustedSize( int ( minWidth ),
                                    int ( prefHeight ),
                                    int ( maxHeight ) ) {
      return m_useSize;
    }

    virtual wxWindow *GetControl() { return this; }

    void SetDateValue( const wxDateTime& date ) {
      if( date.IsValid() ) {
        m_combo->SetText( date.Format( m_format ) );
        SetDate( date );
      } else { // invalid date
        wxASSERT_MSG( HasDPFlag( wxDP_ALLOWNONE ),
                      _T( "this control must have a valid date" ) );
        m_combo->SetText( wxEmptyString );
      }
    }

    bool IsTextEmpty() const {
      return m_combo->GetTextCtrl()->IsEmpty();
    }

    bool ParseDateTime( const wxString& s, wxDateTime* pDt ) {
      wxASSERT( pDt );
      if( !s.empty() ) {
        pDt->ParseFormat( s, m_format );
        if( !pDt->IsValid() ) {
          return false;
        }
      }
      return true;
    }

    void SendDateEvent( const wxDateTime& dt ) {
      //
      // Sends both wxCalendarEvent and wxDateEvent
      wxWindow* datePicker = m_combo->GetParent();
      wxCalendarEvent cev( ( wxCalendarCtrl* ) this, wxEVT_CALENDAR_SEL_CHANGED );
      cev.SetEventObject( datePicker );
      cev.SetId( datePicker->GetId() );
      cev.SetDate( dt );
      datePicker->GetEventHandler()->ProcessEvent( cev );
      wxDateEvent event( datePicker, dt, wxEVT_DATE_CHANGED );
      datePicker->GetEventHandler()->ProcessEvent( event );
    }

  private:

    void OnCalKey( wxKeyEvent & ev ) {
      if( ev.GetKeyCode() == WXK_ESCAPE && !ev.HasModifiers() ) {
        Dismiss();
      } else
      { ev.Skip(); }
    }

    void OnSelChange( wxCalendarEvent &ev ) {
      m_combo->SetText( GetDate().Format( m_format ) );
      if( ev.GetEventType() == wxEVT_CALENDAR_DOUBLECLICKED ) {
        Dismiss();
      }
      SendDateEvent( GetDate() );
    }

    void OnKillTextFocus( wxFocusEvent &ev ) {
      ev.Skip();
      const wxDateTime& dtOld = GetDate();
      wxDateTime dt;
      wxString value = m_combo->GetValue();
      if( !ParseDateTime( value, &dt ) ) {
        if( !HasDPFlag( wxDP_ALLOWNONE ) ) {
          dt = dtOld;
        }
      }
      m_combo->SetText( GetStringValueFor( dt ) );
      if( !dt.IsValid() && HasDPFlag( wxDP_ALLOWNONE ) ) {
        return;
      }
      // notify that we had to change the date after validation
      if( ( dt.IsValid() && ( !dtOld.IsValid() || dt != dtOld ) ) ||
          ( !dt.IsValid() && dtOld.IsValid() ) ) {
        SetDate( dt );
        SendDateEvent( dt );
      }
    }

    bool HasDPFlag( int flag ) {
      return m_combo->GetParent()->HasFlag( flag );
    }

    bool SetFormat( const wxChar *fmt ) {
      m_format.clear();
      wxDateTime dt;
      dt.ParseFormat( wxT( "2003-10-13" ), wxT( "%Y-%m-%d" ) );
      wxString str( dt.Format( fmt ) );
      const wxChar *p = str.c_str();
      while( *p ) {
        int n = wxAtoi( p );
        if( n == dt.GetDay() ) {
          m_format.Append( wxT( "%d" ) );
          p += 2;
        } else if( n == ( int )dt.GetMonth() + 1 ) {
          m_format.Append( wxT( "%m" ) );
          p += 2;
        } else if( n == dt.GetYear() ) {
          m_format.Append( wxT( "%Y" ) );
          p += 4;
        } else if( n == ( dt.GetYear() % 100 ) ) {
          if( HasDPFlag( wxDP_SHOWCENTURY ) ) {
            m_format.Append( wxT( "%Y" ) );
          } else
          { m_format.Append( wxT( "%y" ) ); }
          p += 2;
        } else
        { m_format.Append( *p++ ); }
      }
      if( m_combo ) {
        wxArrayString allowedChars;
        for( wxChar c = _T( '0' ); c <= _T( '9' ); c++ ) {
          allowedChars.Add( wxString( c, 1 ) );
        }
        const wxChar *p2 = m_format.c_str();
        while( *p2 ) {
          if( *p2 == '%' ) {
            p2 += 2;
          } else
          { allowedChars.Add( wxString( *p2++, 1 ) ); }
        }
        #if wxUSE_VALIDATORS
        wxTextValidator tv( wxFILTER_INCLUDE_CHAR_LIST );
        tv.SetIncludes( allowedChars );
        m_combo->SetValidator( tv );
        #endif
        if( GetDate().IsValid() ) {
          m_combo->SetText( GetDate().Format( m_format ) );
        }
      }
      return true;
    }

    virtual void SetStringValue( const wxString& s ) {
      wxDateTime dt;
      if( !s.empty() && ParseDateTime( s, &dt ) ) {
        SetDate( dt );
      }
      //else: keep the old value
    }

    virtual wxString GetStringValue() const {
      return GetStringValueFor( GetDate() );
    }

  private:
    // returns either the given date representation using the current format or
    // an empty string if it's invalid
    wxString GetStringValueFor( const wxDateTime& dt ) const {
      wxString val;
      if( dt.IsValid() ) {
        val = dt.Format( m_format );
      }
      return val;
    }

    wxSize          m_useSize;
    wxString        m_format;

    DECLARE_EVENT_TABLE()
};


BEGIN_EVENT_TABLE( wxCalendarComboPopup, wxCalendarCtrl )
  EVT_KEY_DOWN( wxCalendarComboPopup::OnCalKey )
  EVT_CALENDAR_SEL_CHANGED( wxID_ANY, wxCalendarComboPopup::OnSelChange )
  EVT_CALENDAR_DAY( wxID_ANY, wxCalendarComboPopup::OnSelChange )
  EVT_CALENDAR_MONTH( wxID_ANY, wxCalendarComboPopup::OnSelChange )
  EVT_CALENDAR_YEAR( wxID_ANY, wxCalendarComboPopup::OnSelChange )
  EVT_CALENDAR( wxID_ANY, wxCalendarComboPopup::OnSelChange )
END_EVENT_TABLE()


// ============================================================================
// wxDatePickerCtrlGeneric implementation
// ============================================================================

BEGIN_EVENT_TABLE( wxDatePickerCtrlGeneric, wxDatePickerCtrlBase )
  EVT_TEXT( wxID_ANY, wxDatePickerCtrlGeneric::OnText )
  EVT_SIZE( wxDatePickerCtrlGeneric::OnSize )
  EVT_SET_FOCUS( wxDatePickerCtrlGeneric::OnFocus )
END_EVENT_TABLE()

#ifndef wxHAS_NATIVE_DATEPICKCTRL
IMPLEMENT_DYNAMIC_CLASS( wxDatePickerCtrl, wxControl )
#endif

// ----------------------------------------------------------------------------
// creation
// ----------------------------------------------------------------------------

bool wxDatePickerCtrlGeneric::Create( wxWindow *parent,
                                      wxWindowID id,
                                      const wxDateTime& date,
                                      const wxPoint& pos,
                                      const wxSize& size,
                                      long style,
                                      const wxValidator& validator,
                                      const wxString& name ) {
  wxASSERT_MSG( !( style & wxDP_SPIN ),
                _T( "wxDP_SPIN style not supported, use wxDP_DEFAULT" ) );
  if( !wxControl::Create( parent, id, pos, size,
                          style | wxCLIP_CHILDREN | wxWANTS_CHARS | wxBORDER_NONE,
                          validator, name ) ) {
    return false;
  }
  InheritAttributes();
  m_combo = new wxComboCtrl( this, -1, wxEmptyString,
                             wxDefaultPosition, wxDefaultSize );
  m_combo->SetCtrlMainWnd( this );
  m_popup = new wxCalendarComboPopup();
  m_cal = m_popup;
  m_combo->SetPopupControl( m_popup );
  m_popup->SetDateValue( date.IsValid() ? date : wxDateTime::Today() );
  SetInitialSize( size );
  return true;
}


void wxDatePickerCtrlGeneric::Init() {
  m_combo = NULL;
  m_popup = NULL;
  m_cal = NULL;
}

wxDatePickerCtrlGeneric::~wxDatePickerCtrlGeneric() {
}

bool wxDatePickerCtrlGeneric::Destroy() {
  if( m_combo ) {
    m_combo->Destroy();
  }
  m_combo = NULL;
  m_popup = NULL;
  m_cal = NULL;
  return wxControl::Destroy();
}

// ----------------------------------------------------------------------------
// overridden base class methods
// ----------------------------------------------------------------------------

wxSize wxDatePickerCtrlGeneric::DoGetBestSize() const {
  return m_combo->GetBestSize();
}

// ----------------------------------------------------------------------------
// wxDatePickerCtrlGeneric API
// ----------------------------------------------------------------------------

bool
wxDatePickerCtrlGeneric::SetDateRange( const wxDateTime& lowerdate,
                                       const wxDateTime& upperdate ) {
  return m_popup->SetDateRange( lowerdate, upperdate );
}


wxDateTime wxDatePickerCtrlGeneric::GetValue() const {
  if( HasFlag( wxDP_ALLOWNONE ) && m_popup->IsTextEmpty() ) {
    return wxInvalidDateTime;
  }
  return m_popup->GetDate();
}


void wxDatePickerCtrlGeneric::SetValue( const wxDateTime& date ) {
  m_popup->SetDateValue( date );
}


bool wxDatePickerCtrlGeneric::GetRange( wxDateTime *dt1, wxDateTime *dt2 ) const {
  if( dt1 ) {
    *dt1 = m_popup->GetLowerDateLimit();
  }
  if( dt2 ) {
    *dt2 = m_popup->GetUpperDateLimit();
  }
  return true;
}


void
wxDatePickerCtrlGeneric::SetRange( const wxDateTime &dt1, const wxDateTime &dt2 ) {
  m_popup->SetDateRange( dt1, dt2 );
}

// ----------------------------------------------------------------------------
// event handlers
// ----------------------------------------------------------------------------


void wxDatePickerCtrlGeneric::OnSize( wxSizeEvent& event ) {
  if( m_combo ) {
    m_combo->SetSize( GetClientSize() );
  }
  event.Skip();
}


void wxDatePickerCtrlGeneric::OnText( wxCommandEvent &ev ) {
  ev.SetEventObject( this );
  ev.SetId( GetId() );
  GetParent()->ProcessEvent( ev );
  // We'll create an additional event if the date is valid.
  // If the date isn't valid, the user's probably in the middle of typing
  wxDateTime dt;
  if( !m_popup->ParseDateTime( m_combo->GetValue(), &dt ) ) {
    return;
  }
  m_popup->SendDateEvent( dt );
}


void wxDatePickerCtrlGeneric::OnFocus( wxFocusEvent& ( event ) ) {
  m_combo->SetFocus();
}


#endif // wxUSE_DATEPICKCTRL_GENERIC

#endif // wxUSE_DATEPICKCTRL

